﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Intorise.MiniDB.ClassLibrary
{
    [Serializable]
    public abstract class PersistentObject
    {
        static Dictionary<string, Dictionary<string, MemberInfo>> memberDict = new Dictionary<string, Dictionary<string, MemberInfo>>();
        static Dictionary<string, TypeData> typeDataDict = new Dictionary<string, TypeData>();
        static Dictionary<Guid, long> indexDict = new Dictionary<Guid, long>();

        [NonSerialized]
        private static string name = null;
        [NonSerialized]
        private static TypeData typeData = null;
        [NonSerialized]
        private static object locker = new object();

        private bool persistented = false;

        public PersistentObject()
        {
            ID = Guid.NewGuid();
            Init();
        }

        public Guid ID { get; set; }

        #region repository method
        public void Refresh()
        {

        }

        public void Save()
        {
            if (persistented) { return; }
            BinaryFormatter formatter = new BinaryFormatter();
            try
            {
                using (MemoryStream stream = new MemoryStream())
                {
                    formatter.Serialize(stream, this);
                    using (FileStream fileStream = typeData.DataFileInfo.Open(FileMode.Append, FileAccess.Write))
                    {
                        long length = fileStream.Length;
                        using (FileStream indexFileStream = typeData.IndexFileInfo.Open(FileMode.Append, FileAccess.Write))
                        {
                            byte[] array = ID.ToByteArray();
                            indexFileStream.Write(array, 0, array.Length);
                            array = BitConverter.GetBytes(length);
                            indexFileStream.Write(array, 0, array.Length);
                            length = stream.Length;
                            array = BitConverter.GetBytes(length);
                            indexFileStream.Write(array, 0, array.Length);
                        }
                        stream.WriteTo(fileStream);
                    }
                }
                persistented = true;
            }
            catch
            {
                throw;
            }
        }

        public void Delete()
        {

        }

        public static List<T> Load<T>(Func<T, bool> needLoad) where T : new()
        {
            if (string.IsNullOrEmpty(name))
            {
                T temp = new T();
            }

            List<T> list = new List<T>();
            Dictionary<Guid, Index> dict = new Dictionary<Guid, Index>();
            byte[] array = new byte[16];
            Guid guid;
            long position, size;
            T obj = default(T);

            using (FileStream indexFileStream = typeData.IndexFileInfo.OpenRead())
            {
                do
                {
                    if (indexFileStream.Read(array, 0, 16) == 0) { break; }
                    guid = new Guid(array);
                    if (indexFileStream.Read(array, 0, 8) == 0) { break; }
                    position = BitConverter.ToInt64(array, 0);
                    if (indexFileStream.Read(array, 0, 8) == 0) { break; }
                    size = BitConverter.ToInt64(array, 0);
                    dict.Add(guid, new Index { ID = guid, Position = position, Size = size });
                } while (true);
            }

            BinaryFormatter formatter = new BinaryFormatter();
            array = new byte[10240];
            int readSize = 0;

            using (FileStream fileStream = typeData.DataFileInfo.OpenRead())
            {
                dict.Keys.ToList().ForEach(x =>
                {
                    MemoryStream stream = new MemoryStream();
                    readSize = fileStream.Read(array, 0, (int)dict[x].Size);
                    if (readSize < 0)
                    {
                        throw new Exception("数据文件读取错误，意外的文件结尾！");
                    }

                    stream.Write(array, 0, readSize);
                    stream.Position = 0;
                    obj = (T)formatter.Deserialize(stream);
                    if (needLoad(obj))
                    {
                        list.Add(obj);
                    }
                });
            }

            return list;
        }

        /// <summary>
        /// Load all objects.
        /// </summary>
        /// <returns></returns>
        public static List<T> Load<T>() where T : new()
        {
            return Load((T x) => true);
        }
        #endregion

        #region private data.
        private void Init()
        {
            Type type = this.GetType();
            name = type.FullName;

            if (typeDataDict.ContainsKey(name))
            {
                lock (locker)
                {
                    if (typeDataDict.ContainsKey(name))
                    {
                        return;//初始化过此类型
                    }
                }
            }

            typeData = new TypeData(name, new FileInfo(Helper.DataRootDir + "\\" + name + ".idx"), new FileInfo(Helper.DataRootDir + "\\" + name + ".data"));
            typeDataDict.Add(name, typeData);

            Dictionary<string, MemberInfo> memberDictTemp = new Dictionary<string, MemberInfo>();
            memberDict.Add(type.FullName, memberDictTemp);
            MemberInfo[] memberInfo = type.GetMembers();
            memberInfo.ToList().ForEach(x => memberDictTemp.Add(x.Name, x));

            InitIndex();
            InitData();
        }

        private void InitIndex()
        {
            lock (locker)
            {
                if (!typeData.IndexFileInfo.Exists)
                {
                    using (FileStream stream = typeData.IndexFileInfo.Create()) { }
                }
                else
                {
                    byte[] array = new byte[16];
                    long index = 0;

                    using (FileStream stream = typeData.IndexFileInfo.OpenRead())
                    {
                        while (stream.Read(array, 0, 16) > 0)
                        {
                            Guid guid = new Guid(array);
                            stream.Read(array, 0, sizeof(long));
                            index = BitConverter.ToInt64(array, 0);
                            indexDict.Add(guid, index);
                        }
                    }
                }
            }
        }

        private void InitData()
        {
            lock (locker)
            {
                if (!typeData.DataFileInfo.Exists)
                {
                    using (FileStream stream = typeData.DataFileInfo.Create()) { }
                }
            }
        }
        #endregion
    }
}